elk_jquery_embed.js ➔ scrollEmbed   F
last analyzed

Complexity

Conditions 42

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 42
eloc 30
dl 0
loc 53
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like elk_jquery_embed.js ➔ scrollEmbed often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * @package   ElkArte Forum
3
 * @copyright ElkArte Forum contributors
4
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
5
 *
6
 * @version 2.0 dev
7
 *
8
 * Original code from Aziz, redone and refactored for ElkArte
9
 */
10
11
/** global: elk_session_id, elk_session_var, elk_scripturl */
12
13
/**
14
 * This javascript searches the message for video links and replaces them
15
 * with a clickable preview thumbnail of the video.  Once the image is clicked
16
 * the video is embedded in to the page to play.
17
 *
18
 * Currently, works with YouTube, Vimeo, TikTok, Twitter, Facebook, Instagram and DailyMotion
19
 *
20
 */
21
(function($) {
22
	'use strict';
23
24
	/**
25
	 * @param {object} oInstanceSettings holds the text strings to use in the html created
26
	 * @param {int} msgid optional to only search for links in a specific id
27
	 */
28
	$.fn.linkifyvideo = function(oInstanceSettings, msgid) {
29
		let oDefaultsSettings = {
30
			embed_limit: 25,
31
			preview_image: '',
32
			ctp_video: '',
33
			hide_video: '',
34
			youtube: '',
35
			vimeo: '',
36
			dailymotion: '',
37
			tiktok: '',
38
			twitter: '',
39
			facebook: '',
40
			instagram: ''
41
		};
42
43
		// Account for user options
44
		let oSettings = $.extend({}, oDefaultsSettings, oInstanceSettings || {});
45
46
		/**
47
		 * Replaces the image with the created embed code to show the video
48
		 * Called from click event attached to the image
49
		 *
50
		 * @param {string} tag anchor tag we are replacing with the embed tag
51
		 * @param {string} eURL the load or place source link
52
		 * @param {boolean} bAspect if to use a tall vs wide
53
		 */
54
		function showEmbed (tag, eURL, bAspect)
55
		{
56
			if (bAspect)
57
			{
58
				$(tag).html(embed_html.replace('{src}', eURL));
0 ignored issues
show
Bug introduced by
The local (let) variable embed_html is used before it is defined. This will cause a reference error.
Loading history...
59
			}
60
			else
61
			{
62
				$(tag).html(embed_html_916.replace('{src}', eURL));
0 ignored issues
show
Bug introduced by
The local (let) variable embed_html_916 is used before it is defined. This will cause a reference error.
Loading history...
63
			}
64
		}
65
66
		/**
67
		 * Shows the video image and sets up the link
68
		 * Sets click event to load video sites embed code
69
		 *
70
		 * @param {object} a videoID link
71
		 * @param {string} src source of image
72
		 * @param {string} eURLa play link
73
		 * @param {boolean} bAspect false to use a 9/16 iframe vs 16x9
74
		 */
75
		function getIMG (a, src, eURLa, bAspect)
76
		{
77
			return $('' +
78
				'<div class="elk_video">' +
79
				'   <a href="' + a.href + '">' +
80
				'       <img class="elk_video_preview" alt="' + oSettings.preview_image + '" ' + 'title="' + oSettings.ctp_video + '" src="' + src + '"/>' +
81
				'   </a>' +
82
				'</div>')
83
				.on('click', function(e) {
84
						e.preventDefault();
85
						let tag = this;
86
						showEmbed(tag, eURLa, bAspect);
87
					}
88
				);
89
		}
90
91
		/**
92
		 * Returns a linked preview image.  Click on the image to load the player.
93
		 *
94
		 * @param {string} a link tag of the video
95
		 * @param {string} src link of the preview image
96
		 * @param {string} eURLa single click event play video
97
		 * @param {boolean} bAspect use a wide vs tall ratio
98
		 */
99
		function embedIMG (a, src, eURLa, bAspect = true)
100
		{
101
			return getIMG(a, src, eURLa, bAspect);
102
		}
103
104
		/**
105
		 * Creates and inserts a document fragment.  Doing this vs inner/outer HTML ensures that any script
106
		 * tags in the embed code will execute.
107
		 *
108
		 * @param {Element} a the link we are working with
109
		 * @param {object} data the data from the ajax call
110
		 */
111
		function createFragment (a, data)
112
		{
113
			// Since data.html may contain a script tag that needs to run, we have to add it like this
114
			let parent = a.parentNode,
115
				frag = document.createRange().createContextualFragment('<div class="elk_video">' + data.html + '</div>');
116
117
			parent.parentNode.appendChild(frag);
118
			parent.nextSibling.outerHTML = '';
119
		}
120
121
		/**
122
		 * Converts a given time value from an array to seconds using a multiplier.
123
		 *
124
		 * @param {Array} timeArray - The array containing time values.
125
		 * @param {number} timeIndex - The index of the time value to convert.
126
		 * @param {number} multiplier - The multiplier to apply to the converted time value.
127
		 * @return {number} - The converted time value in seconds, or 0 if the time array is undefined or the timeIndex is out of range.
128
		 */
129
		function convertToSeconds (timeArray, timeIndex, multiplier)
130
		{
131
			if (typeof timeArray[timeIndex] !== 'undefined')
132
			{
133
				return parseInt(timeArray[timeIndex]) * multiplier;
134
			}
135
136
			return 0;
137
		}
138
139
		// The embed code
140
		let domain_regex = /^[^:]*:\/\/(?:www\.)?([^\/]+)(\/.*)$/,
141
			embedded_count = 0,
142
			provider_class = '',
143
			embed_html = '<iframe width="640" height="360" src="{src}" data-autoplay="true" allow="fullscreen" loading="lazy" type="text/html"></iframe>',
0 ignored issues
show
Unused Code introduced by
The variable embed_html seems to be never used. Consider removing it.
Loading history...
144
			embed_html_916 = '<iframe width="480" height="800" src="{src}" allow="fullscreen" loading="lazy" type="text/html"></iframe>',
0 ignored issues
show
Unused Code introduced by
The variable embed_html_916 seems to be never used. Consider removing it.
Loading history...
145
			handlers = {},
146
			imgHandlers = {},
147
			logos = {
148
				tiktok: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 48 48\'%3E%3Cpath fill=\'%23fff\' fill-opacity=\'.01\' d=\'M0 0h48v48H0z\'/%3E%3Cpath fill=\'%232F88FF\' stroke=\'%23000\' stroke-linejoin=\'round\' stroke-width=\'3.833\' d=\'M21.358 19.14c-5.888-.284-9.982 1.815-12.28 6.299-3.446 6.724-.597 17.728 10.901 17.728 11.499 0 11.831-11.111 11.831-12.276V17.876c2.46 1.557 4.533 2.495 6.221 2.813 1.688.317 2.76.458 3.219.422v-6.476c-1.561-.188-2.911-.547-4.05-1.076-1.709-.794-5.096-2.997-5.096-6.226.002.016.002-.817 0-2.499h-7.118c-.021 15.816-.021 24.502 0 26.058.032 2.334-1.779 5.6-5.45 5.6-3.672 0-5.482-3.263-5.482-5.367 0-1.288.442-3.155 2.271-4.538 1.085-.82 2.59-1.147 5.033-1.147V19.14Z\'/%3E%3C/svg%3E',
149
				vimeo: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 455.731 455.731\'%3E%3Cpath fill=\'%231ab7ea\' d=\'M0 0h455.731v455.731H0z\'/%3E%3Cpath fill=\'%23fff\' d=\'m49.642 157.084 17.626 22.474s22.033-17.186 29.965-17.186c4.927 0 15.423 5.729 22.033 25.558 6.61 19.83 34.441 122.62 36.134 127.351 7.607 21.26 17.626 60.811 48.473 66.54s70.065-25.558 91.657-48.473c21.592-22.914 106.64-120.741 110.165-179.349 3.26-54.191-14.517-66.765-22.474-71.828-14.542-9.254-38.778-12.338-61.692-4.407s-57.726 33.931-66.98 80.2c0 0 31.287-11.457 42.744-.441s8.373 35.253-1.322 53.32-37.015 59.93-47.151 61.252c-10.135 1.322-18.067-18.508-19.389-23.796-1.322-5.288-18.067-77.997-24.236-120.3s-33.049-49.354-45.829-49.354c-12.779.001-34.812 9.696-109.724 78.439z\'/%3E%3C/svg%3E',
150
				dailymotion: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 512 512\'%3E%3Cpath fill=\'%230066DC\' fill-rule=\'evenodd\' d=\'M0 512h512V0H0v512Zm441.5-68.635h-76.314v-29.928c-23.443 22.945-47.385 31.424-79.308 31.424-32.421 0-60.354-10.474-83.797-31.424-30.926-27.433-46.887-63.346-46.887-105.245 0-38.407 14.965-72.823 42.896-99.758 24.94-24.44 55.367-36.91 89.284-36.91 32.422 0 57.361 10.973 75.318 33.917V88.724L441.5 72.395v370.97Zm-141.157-202.01c-37.41 0-66.339 30.426-66.339 66.338 0 37.41 28.93 65.841 69.332 65.841 33.918 0 62.349-27.932 62.349-64.843 0-38.406-28.431-67.336-65.342-67.336Z\'/%3E%3C/svg%3E',
151
				twitter: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'126 2 589 589\'%3E%3Ccircle cx=\'420.944\' cy=\'296.781\' r=\'294.5\' fill=\'%232daae1\'/%3E%3Cpath fill=\'%23fff\' d=\'M609.773 179.634c-13.891 6.164-28.811 10.331-44.498 12.204 16.01-9.587 28.275-24.779 34.066-42.86a154.78 154.78 0 0 1-49.209 18.801c-14.125-15.056-34.267-24.456-56.551-24.456-42.773 0-77.462 34.675-77.462 77.473 0 6.064.683 11.98 1.996 17.66-64.389-3.236-121.474-34.079-159.684-80.945-6.672 11.446-10.491 24.754-10.491 38.953 0 26.875 13.679 50.587 34.464 64.477a77.122 77.122 0 0 1-35.097-9.686v.979c0 37.54 26.701 68.842 62.145 75.961-6.511 1.784-13.344 2.716-20.413 2.716-4.998 0-9.847-.473-14.584-1.364 9.859 30.769 38.471 53.166 72.363 53.799-26.515 20.785-59.925 33.175-96.212 33.175-6.25 0-12.427-.373-18.491-1.104 34.291 21.988 75.006 34.824 118.759 34.824 142.496 0 220.428-118.052 220.428-220.428 0-3.361-.074-6.697-.236-10.021a157.855 157.855 0 0 0 38.707-40.158z\'/%3E%3C/svg%3E',
152
				facebook: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 128 128\'%3E%3Cpath fill=\'%233B5998\' d=\'M126 118a8 8 0 0 1-8 8H10a8 8 0 0 1-8-8V10a8 8 0 0 1 8-8h108a8 8 0 0 1 8 8v108z\'/%3E%3Cpath fill=\'%236D84B4\' d=\'M5.667 98.98h116.666v18.039H5.667z\'/%3E%3Cpath fill=\'%23FFF\' d=\'M93.376 117.012H72.203V65.767H61.625v-17.66h10.578V37.504c0-14.407 5.973-22.974 22.943-22.974h14.128v17.662h-8.831c-6.606 0-7.043 2.468-7.043 7.074l-.024 8.839h15.998l-1.872 17.66H93.376v51.247z\'/%3E%3C/svg%3E',
153
				instagram: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 256 256\'%3E%3Cpath fill=\'%230A0A08\' d=\'M128 23.064c34.177 0 38.225.13 51.722.745 12.48.57 19.258 2.655 23.769 4.408 5.974 2.322 10.238 5.096 14.717 9.575 4.48 4.479 7.253 8.743 9.575 14.717 1.753 4.511 3.838 11.289 4.408 23.768.615 13.498.745 17.546.745 51.723 0 34.178-.13 38.226-.745 51.723-.57 12.48-2.655 19.257-4.408 23.768-2.322 5.974-5.096 10.239-9.575 14.718-4.479 4.479-8.743 7.253-14.717 9.574-4.511 1.753-11.289 3.839-23.769 4.408-13.495.616-17.543.746-51.722.746-34.18 0-38.228-.13-51.723-.746-12.48-.57-19.257-2.655-23.768-4.408-5.974-2.321-10.239-5.095-14.718-9.574-4.479-4.48-7.253-8.744-9.574-14.718-1.753-4.51-3.839-11.288-4.408-23.768-.616-13.497-.746-17.545-.746-51.723 0-34.177.13-38.225.746-51.722.57-12.48 2.655-19.258 4.408-23.769 2.321-5.974 5.095-10.238 9.574-14.717 4.48-4.48 8.744-7.253 14.718-9.575 4.51-1.753 11.288-3.838 23.768-4.408 13.497-.615 17.545-.745 51.723-.745M128 0C93.237 0 88.878.147 75.226.77c-13.625.622-22.93 2.786-31.071 5.95-8.418 3.271-15.556 7.648-22.672 14.764C14.367 28.6 9.991 35.738 6.72 44.155 3.555 52.297 1.392 61.602.77 75.226.147 88.878 0 93.237 0 128c0 34.763.147 39.122.77 52.774.622 13.625 2.785 22.93 5.95 31.071 3.27 8.417 7.647 15.556 14.763 22.672 7.116 7.116 14.254 11.492 22.672 14.763 8.142 3.165 17.446 5.328 31.07 5.95 13.653.623 18.012.77 52.775.77s39.122-.147 52.774-.77c13.624-.622 22.929-2.785 31.07-5.95 8.418-3.27 15.556-7.647 22.672-14.763 7.116-7.116 11.493-14.254 14.764-22.672 3.164-8.142 5.328-17.446 5.95-31.07.623-13.653.77-18.012.77-52.775s-.147-39.122-.77-52.774c-.622-13.624-2.786-22.929-5.95-31.07-3.271-8.418-7.648-15.556-14.764-22.672C227.4 14.368 220.262 9.99 211.845 6.72c-8.142-3.164-17.447-5.328-31.071-5.95C167.122.147 162.763 0 128 0Zm0 62.27C91.698 62.27 62.27 91.7 62.27 128c0 36.302 29.428 65.73 65.73 65.73 36.301 0 65.73-29.428 65.73-65.73 0-36.301-29.429-65.73-65.73-65.73Zm0 108.397c-23.564 0-42.667-19.103-42.667-42.667S104.436 85.333 128 85.333s42.667 19.103 42.667 42.667-19.103 42.667-42.667 42.667Zm83.686-110.994c0 8.484-6.876 15.36-15.36 15.36-8.483 0-15.36-6.876-15.36-15.36 0-8.483 6.877-15.36 15.36-15.36 8.484 0 15.36 6.877 15.36 15.36Z\'/%3E%3C/svg%3E',
154
				youtube: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'-100 -100 661 661\'%3E%3Cpath d=\'M365.257 67.393H95.744C42.866 67.393 0 110.259 0 163.137v134.728c0 52.878 42.866 95.744 95.744 95.744h269.513c52.878 0 95.744-42.866 95.744-95.744V163.137c0-52.878-42.866-95.744-95.744-95.744zm-64.751 169.663-126.06 60.123c-3.359 1.602-7.239-.847-7.239-4.568V168.607c0-3.774 3.982-6.22 7.348-4.514l126.06 63.881c3.748 1.899 3.683 7.274-.109 9.082z\' style=\'fill:%23f61c0d\'/%3E%3C/svg%3E',
155
			};
156
157
		// Get a Youtube video thumbnail
158
		imgHandlers.getYoutubeIMG = function(eURL, callback) {
159
			fetchDocument(eURL, ytResponse, 'json');
160
161
			function ytResponse (data)
162
			{
163
				if (typeof data.thumbnail_url === 'undefined')
164
				{
165
					callback(logos.youtube);
166
				}
167
				else
168
				{
169
					callback(data.thumbnail_url);
170
				}
171
			}
172
		};
173
174
		// Get a twitter embed html
175
		imgHandlers.getTwitterEmbed = function(eURL, callback) {
176
			fetchDocument(eURL, twResponse, 'json');
177
178
			function twResponse (data)
179
			{
180
				if (typeof data.html === 'undefined')
181
				{
182
					data.html = '';
183
				}
184
185
				callback(data);
186
			}
187
		};
188
189
		// Get a TikTok video thumbnail and embed data
190
		imgHandlers.getTikTokEmbed = function(eURL, callback) {
191
			fetchDocument(eURL, ttResponse, 'json', false);
192
193
			function ttResponse (data)
194
			{
195
				if (typeof data.html === 'undefined')
196
				{
197
					data.thumbnail_url = logos.tiktok;
198
					data.html = '';
199
				}
200
201
				callback(data);
202
			}
203
		};
204
205
		// Get a dailymotion video thumbnail
206
		imgHandlers.getDailymotionIMG = function(eURL, callback) {
207
			fetchDocument(eURL, dailyResponse, 'json', false);
208
209
			function dailyResponse (data)
210
			{
211
				if (typeof data.thumbnail_480_url === 'undefined')
212
				{
213
					callback(logos.dailymotion);
214
				}
215
				else
216
				{
217
					callback(data.thumbnail_480_url);
218
				}
219
			}
220
		};
221
222
		// Get a Vimeo video thumbnail
223
		imgHandlers.getVimeoIMG = function(eURL, callback) {
224
			fetchDocument(eURL, vimeoResponse, 'json', false);
225
226
			function vimeoResponse (data)
227
			{
228
				if (typeof data[0].thumbnail_large === 'undefined')
229
				{
230
					callback(logos.vimeo);
231
				}
232
				else
233
				{
234
					callback(data[0].thumbnail_large);
235
				}
236
			}
237
		};
238
239
		// Youtube and variants
240
		handlers['youtube.com'] = function(path, a) {
241
			let videoID = path.match(/\bv[=/]([^&#?$]+)/i) || path.match(/#p\/(?:a\/)?[uf]\/\d+\/([^?$]+)/i) || path.match(/(?:\/)([\w-]{11})/i);
242
243
			if (!videoID || !(videoID = videoID[1]))
244
			{
245
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
246
			}
247
248
			// There are two types of YouTube timestamped links
249
			// http://youtu.be/lLOE3fBZcUU?t=1m37s when you click share underneath the video
250
			// http://youtu.be/lLOE3fBZcUU?t=97 when you right click on a video and choose "Copy video URL at current time"
251
			// For embedding, you need to use "?start=97" instead, so we have to convert t=1m37s to seconds while also supporting t=97
252
			let startAt = path.match(/t=(?:([1-9]{1,2})h)?(?:([1-9]{1,2})m)?(?:([1-9]+)s?)/),
253
				startAtPar = '';
254
255
			if (startAt)
256
			{
257
				let startAtSeconds = 0;
258
259
				startAtSeconds += convertToSeconds(startAt, 1, 3600);  // Hours
260
				startAtSeconds += convertToSeconds(startAt, 2, 60);    // Minutes
261
				startAtSeconds += convertToSeconds(startAt, 3, 1);     // Seconds
262
263
				startAtPar = '&start=' + startAtSeconds.toString();
264
			}
265
266
			let embedURL = '//www.youtube-nocookie.com/embed/' + videoID + '?rel=0' + startAtPar,
267
				imgURL = '//www.youtube.com/oembed?url=https://www.youtube.com/watch?v=' + videoID + '&format=json',
268
				tag = embedIMG(a, '//i.ytimg.com/vi/' + videoID + '/sddefault.jpg', embedURL + '&autoplay=1');
269
270
			// Get the preview image / embed tag
271
			imgHandlers.getYoutubeIMG(imgURL, function(img) {
272
				$(a).parent().next().find('img').attr('src', img);
273
			});
274
275
			return [oSettings.youtube, tag];
276
		};
277
		handlers['m.youtube.com'] = handlers['youtube.com'];
278
		handlers['youtu.be'] = handlers['youtube.com'];
279
280
		// Vimeo
281
		handlers['vimeo.com'] = function(path, a) {
282
			let videoID = path.match(/^\/(\d+)/i);
283
284
			if (!videoID || !(videoID = videoID[1]))
285
			{
286
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
287
			}
288
289
			let embedURL = '//player.vimeo.com/video/' + videoID,
290
				imgURL = '//vimeo.com/api/v2/video/' + videoID + '.json',
291
				tag;
292
293
			tag = embedIMG(a, logos.vimeo, embedURL + '?autoplay=1');
294
295
			// Get the preview image / embed tag
296
			imgHandlers.getVimeoIMG(imgURL, function(img) {
297
				$(a).parent().next().find('img').attr('src', img);
298
			});
299
300
			return [oSettings.vimeo, tag];
301
		};
302
303
		// Dailymotion
304
		handlers['dailymotion.com'] = function(path, a) {
305
			let videoID = path.match(/^\/video\/([a-z0-9]{1,18})/i);
306
307
			if (!videoID || videoID[1] === '')
308
			{
309
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
310
			}
311
312
			let embedURL = '//dailymotion.com/embed/video/' + videoID[1],
313
				imgURL = '//api.dailymotion.com/video/' + videoID[1] + '?fields=thumbnail_480_url',
314
				tag;
315
316
			tag = embedIMG(a, logos.dailymotion, embedURL + '?related=0&autoplay=1');
317
318
			// Get the preview image or embed tag
319
			imgHandlers.getDailymotionIMG(imgURL, function(img) {
320
				$(a).parent().next().find('img').attr('src', img);
321
			});
322
323
			return [oSettings.dailymotion, tag];
324
		};
325
326
		// TikTok
327
		handlers['tiktok.com'] = function(path, a) {
328
			let videoID = path.match(/^\/@([0-9A-Za-z_\-.]*)\/video\/([0-9]*)/i);
329
330
			if (!videoID)
331
			{
332
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
333
			}
334
335
			let embedURL = '//www.tiktok.com/oembed?url=https://www.tiktok.com/@' + videoID[1] + '/video/' + videoID[2],
336
				tag;
337
338
			imgHandlers.getTikTokEmbed(embedURL, function(data) {
339
				$(a).parent().next().find('img').attr('src', data.thumbnail_url);
340
				a.embedURL = data.html;
341
			});
342
343
			tag = embedIMG(a, logos.tiktok, embedURL);
344
345
			// Change the default click event to one that replaces the markup
346
			tag.off('click', '**', false);
347
			tag.on('click', function(e) {
348
				e.preventDefault();
349
				let load = $(a).parent();
350
				load.parent().addClass('portrait');
351
352
				load.next().replaceWith('<div class="elk_video">' + a.embedURL + '</div>');
353
			});
354
355
			return [oSettings.tiktok, tag];
356
		};
357
358
		// Twitter
359
		handlers['twitter.com'] = function(path, a) {
360
			let videoID = path.match(/\/status\/([0-9]{16,20})/);
361
362
			if (!videoID || videoID[1] === '')
363
			{
364
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
365
			}
366
367
			let embedURL = elk_prepareScriptUrl(elk_scripturl) + 'action=xmlhttp;sa=videoembed;api=json;site=twitter;videoid=' + videoID[1] + ';' + elk_session_var + '=' + elk_session_id,
368
				tag;
369
370
			tag = embedIMG(a, logos.twitter, embedURL);
371
372
			// Twitter has its own embed codes we need to load, no preview here, replace click event as well
373
			tag.off('click', '**', false);
374
			a.embedURL = embedURL;
375
			a.setAttribute('data-video_embed', 'getTwitterEmbed');
376
377
			return [oSettings.twitter, tag, 'portrait'];
378
		};
379
380
		// Facebook
381
		handlers['facebook.com'] = function(path, a) {
382
			let videoID = path.match(/([\d\w._-]+)?(?:\/videos\/|\/video.php\?v=)(\d+)/i);
383
384
			if (!videoID || videoID[1] === '' || videoID[2] === '')
385
			{
386
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
387
			}
388
389
			let embedURL = '//www.facebook.com/plugins/video.php?href=https://www.facebook.com/' + videoID[1] + '/videos/' + videoID[2],
390
				tag;
391
392
			tag = embedIMG(a, logos.facebook, embedURL + '?related=0&autoplay=1');
393
394
			return [oSettings.facebook, tag];
395
		};
396
397
		// Instagram
398
		handlers['instagram.com'] = function(path, a) {
399
			let videoID = path.match(/\/(?:tv|p)\/([a-z0-9]{10,18})(?:\/\?|\/)?/i);
400
401
			if (!videoID || videoID[1] === '')
402
			{
403
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
404
			}
405
406
			let embedURL = '//www.instagram.com/p/' + videoID[1] + '/embed',
407
				tag;
408
409
			tag = embedIMG(a, logos.instagram, embedURL + '?related=0&autoplay=1', false);
410
411
			return [oSettings.instagram, tag, 'portrait'];
412
		};
413
414
		// ---------------------------------------------------------------------------
415
		// Get the bbc_link links in the id="msg_1234 divs.
416
		let links;
417
418
		if (typeof msgid !== 'undefined')
419
		{
420
			links = document.querySelectorAll('#' + msgid + ' a.bbc_link');
421
		}
422
		else
423
		{
424
			links = document.querySelectorAll('[id^=msg_] a.bbc_link');
425
		}
426
427
		// Create the show/hide button
428
		let showhideBtn = $('' +
429
			'<a class="floatright" title="' + oSettings.hide_video + '">' +
430
			'   <i class="icon icon-small i-caret-up" alt=">"></i>' +
431
			'</a>')
432
			.on('click', function() {
433
				let $img = $(this).find('i'), // The open / close icon
434
					$vid = $(this).parent().next(); // The immediate elk_video div
435
436
				// Toggle slide the video and change the icon
437
				$img.attr('class', 'icon icon-small ' + ($vid.is(':hidden') !== true ? 'i-caret-down' : 'i-caret-up'));
438
				$vid.slideToggle();
439
			});
440
441
		// Loop though each link
442
		links.forEach((link) => {
443
			let tag = link,
444
				text = tag.innerText || tag.textContent || '';
445
446
			// Ignore in sentences
447
			if (tag.previousSibling && tag.previousSibling.nodeName === '#text' && tag.previousSibling.nodeValue !== ' ')
448
			{
449
				return;
450
			}
451
452
			// Ignore in quotes and signatures
453
			if ('bbc_quote;signature'.indexOf(tag.parentNode.className) !== -1)
454
			{
455
				return;
456
			}
457
458
			// No href or inner text not equal to href attr then we move along
459
			if (tag.href === '' || tag.href.indexOf(text) !== 0)
460
			{
461
				return;
462
			}
463
464
			// Get domain and validate we know how to handle it
465
			let m = tag.href.match(domain_regex),
466
				handler = null,
0 ignored issues
show
Unused Code introduced by
The assignment to handler seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
467
				args = null;
0 ignored issues
show
Unused Code introduced by
The assignment to args seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
468
469
			// One of our video provider domains?
470
			if (embedded_count < oSettings.embed_limit && m !== null && typeof handlers[m[1]] !== 'undefined' && handlers[m[1]] !== null)
471
			{
472
				// Call the handler and get the tag to insert
473
				handler = handlers[m[1]];
474
475
				// If there are video tags seperated by only a BR node, remove the BR so the video embed can
476
				// be side by side on a wide enough screen.
477
				if (tag.previousSibling && tag.previousSibling.nodeName === 'BR')
478
				{
479
					if (tag.previousSibling.previousElementSibling && tag.previousSibling.previousElementSibling.classList.contains('elk_video_container'))
480
					{
481
						tag.previousSibling.remove();
482
					}
483
				}
484
485
				args = handler(m[2], tag, provider_class);
486
				if (args)
487
				{
488
					embedded_count++;
489
					$(tag).wrap('<div class="elk_video_container ' + (typeof args[2] !== 'undefined' ? args[2] : '') + '">');
490
					$(tag).wrap('<div class="elk_video_header">').text(args[0]).after(showhideBtn.clone(true));
491
					$(tag).parent().parent().append(args[1]);
492
				}
493
			}
494
		});
495
496
		// If we have embeded videos, add the lazy load code and events
497
		if (embedded_count > 0)
498
		{
499
			scrollEmbed();
500
		}
501
502
		/**
503
		 * Some sites have no thumbnail, so we mimic an onclick to load the embed when the element is on screen.  This
504
		 * provides something other than the default logo.
505
		 *
506
		 * Note: This does now work for all sites, like instagram, due to cors errors.  For those you need to set the onclick
507
		 * and let the user load the embed.
508
		 */
509
		function scrollEmbed ()
510
		{
511
			let videoLinks = document.querySelectorAll('a[data-video_embed]'),
512
				throttleTimeout,
513
				found = false;
514
515
			/**
516
			 * Function that fires to lazy load video sites embed when in viewport
517
			 */
518
			function videoLinksListener ()
519
			{
520
				if (throttleTimeout)
521
				{
522
					clearTimeout(throttleTimeout);
523
				}
524
525
				// On scroll fires "a lot" so this tames it to be less abusive
526
				throttleTimeout = setTimeout(function() {
527
					videoLinks.forEach(function(a) {
528
						// No links remaining, drop any listeners
529
						if (videoLinks.length === 0)
530
						{
531
							document.removeEventListener('scroll', videoLinksListener);
532
							window.removeEventListener('resize', videoLinksListener);
533
							window.removeEventListener('orientationChange', videoLinksListener);
534
						}
535
536
						// Hey I see you ...
537
						if (isElementInViewport(a))
538
						{
539
							let func = a.getAttribute('data-video_embed');
540
							found = true;
541
							a.removeAttribute('data-video_embed');
542
							imgHandlers[func](a.embedURL, (data) => {
543
								createFragment(a, data);
544
							});
545
						}
546
					});
547
548
					// Once a link is found, lets not try that one again.
549
					if (found)
550
					{
551
						videoLinks = document.querySelectorAll('a[data-video_embed]');
552
					}
553
				}, 25);
554
			}
555
556
			// Scroll, rotate or resize, we check if we can see the video link.
557
			document.addEventListener('scroll', videoLinksListener);
558
			window.addEventListener('DOMContentLoaded', videoLinksListener);
559
			window.addEventListener('resize', videoLinksListener);
560
			window.addEventListener('orientationChange', videoLinksListener);
561
		}
562
	};
563
})(jQuery);
564